package com.zp.framework.jpa;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.zp.util.Constant;
import com.zp.util.MyException;

/**
 * implement the JPA DAO interface
 * 
 * @author chenlijun08@gmail.com
 * 
 * @version 1.0
 * 
 * @date 2014/2/6
 * 
 */
public class JpaDao implements IJpaDao {
	private static final Log logger = LogFactory.getLog(JpaDao.class);

	@PersistenceContext
	private EntityManager entityManager;

	@Override
	public void clear() throws MyException {
		try {
			entityManager.clear();
			logger.info("Flush cache sucssfully");
		} catch (Exception e) {
			logger.error("Flush cache failed");
			throw new MyException(e.getMessage());
		}
	}

	@Override
	public void save(Object entity) throws MyException {
		try {
			entityManager.persist(entity);
			logger.info("Save object sucssfully");
		} catch (Exception e) {
			logger.error("Save object failed");
			throw new MyException(e.getMessage());
		}
	}

	@Override
	public Object update(Object entity) throws MyException {
		Object result = Constant.OBJECT_NULL;
		try {
			result = entityManager.merge(entity);
			logger.info("Update object sucssfully");
		} catch (Exception e) {
			logger.error("Update object failed");
			throw new MyException(e.getMessage());
		}
		return result;
	}

	@Override
	public void deleteObject(Object entity) throws MyException {
		try {
			entityManager.remove(entity);
			logger.info("Delete object sucssfully");
		} catch (Exception e) {
			logger.error("Delete object failed");
			throw new MyException(e.getMessage());
		}
	}

	@Override
	public void deleteById(Class<?> entityClass, Object id) throws MyException {
		Object obj = this.findById(entityClass, id);
		if (obj != Constant.OBJECT_NULL) {
			this.deleteObject(obj);

		}
	}

	@Override
	public void deleteFromCriteria(String jpql, Object[] queryParams)
			throws MyException {
		List<?> list = this.getScrollData(Constant.NEGATIVE_ONE,
				Constant.NEGATIVE_ONE, jpql, queryParams);
		for (Object object : list) {
			this.deleteObject(object);
		}
	}

	@Override
	public Object findById(Class<?> entityClass, Object id) throws MyException {
		Object result = Constant.OBJECT_NULL;
		try {
			result = entityManager.find(entityClass, id);
			logger.info("Object is found from:" + entityClass + " with " + id);
		} catch (Exception e) {
			logger.error("Object is not exits from:" + entityClass + " with "
					+ id);
			throw new MyException(e.getMessage());
		}
		return result;
	}

	/**
	 * get all the data records with entity class
	 * 
	 * @param entityClass
	 * 
	 * @return List<?>
	 * 
	 * @exception PersistenceException
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public List<?> getResultData(final Class<?> entityClass)
			throws PersistenceException {
		List<?> resultList = Constant.LIST_NULL;
		try {
			CriteriaBuilder cb = entityManager.getCriteriaBuilder();
			CriteriaQuery<?> criteria = cb.createQuery(entityClass);
			Root root = criteria.from(entityClass);
			criteria.select(root).distinct(true);
			resultList = entityManager.createQuery(criteria).getResultList();
			logger.info("Get data with class,result is:"
					+ resultList.toArray().toString());
		} catch (PersistenceException pe) {
			logger.error("Get data with class:get data failed please see below information");
			throw new PersistenceException(pe.getMessage());
		}

		return resultList;

	}

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public List<?> getResultData(int pageNo, int pageSize, String jpql,
			Object[] queryParams) throws MyException {
		return this.getScrollData(pageNo, pageSize, jpql, queryParams);
	}

	/**
	 * get count data with id JPQL like "select id from User where ...."
	 * 
	 * @param jpql
	 * 
	 * @param queryParams
	 * 
	 * @return the count of result data
	 * 
	 * @exception PersistenceException
	 */
	@Override
	public long getCount(final String jpql, final Object[] queryParams)
			throws PersistenceException {
		logger.info("Get count with Jpql:" + jpql);
		Object rawCount = Constant.ZERO;
		try {
			Query query = entityManager.createQuery(jpql);
			setQueryParams(query, queryParams);
			rawCount = query.getSingleResult();
		} catch (PersistenceException pe) {
			logger.error("Get count with Jpql:get data rows failed,plese check below information");
			throw new PersistenceException(pe.getMessage());
		}

		return (Long) rawCount;
	}

	/**
	 * get result from native SQL
	 * 
	 * @param pageNo
	 *            the page index
	 * 
	 * @param pageSize
	 *            how many rows you want to fetch for one page
	 * 
	 * @param sql
	 * 
	 * @param queryParams
	 * 
	 * @return List<?>
	 * 
	 * @exception PersistenceException
	 */
	@Override
	public List<?> findByNativeQuery(final int pageNo, final int pageSize,
			final String sql, final Object[] queryParams)
			throws PersistenceException {
		logger.info("Get data with native SQL:" + sql);
		List<?> resultList = Constant.LIST_NULL;
		try {
			Query query = entityManager.createNativeQuery(sql);
			setQueryParams(query, queryParams);
			if (pageNo != Constant.NEGATIVE_ONE
					&& pageSize != Constant.NEGATIVE_ONE) {
				query.setFirstResult((pageNo - Constant.ONE) * pageSize)
						.setMaxResults(pageSize);
				query.setHint("org.hibernate.cacheable", true);
			} else {
				logger.info("Get data with native SQL:page is not used pageNo is "
						+ pageNo + " pageSize is " + pageSize);
			}
			resultList = query.getResultList();
		} catch (PersistenceException pe) {
			logger.error("Get data with native SQL:get result data failed with native sql,please check below information");
			throw new PersistenceException(pe.getMessage());
		}

		return resultList;
	}

	/**
	 * get result from JPQL
	 * 
	 * @param pageNo
	 *            the page index
	 * 
	 * @param pageSize
	 *            how many rows you want to fetch for one page
	 * 
	 * @param jpql
	 * 
	 * @param queryParams
	 * 
	 * @return List<?>
	 * 
	 * @exception PersistenceException
	 */
	protected List<?> getScrollData(final int pageNo, final int pageSize,
			final String jpql, final Object[] queryParams)
			throws PersistenceException {
		logger.info("Get scrollData with Jpql:" + jpql);
		List<?> resultData = Constant.LIST_NULL;
		try {
			Query query = entityManager.createQuery(jpql);
			setQueryParams(query, queryParams);
			if (pageNo != Constant.NEGATIVE_ONE
					&& pageSize != Constant.NEGATIVE_ONE) {
				query.setFirstResult((pageNo - Constant.ONE) * pageSize)
						.setMaxResults(pageSize);
				query.setHint("org.hibernate.cacheable", true);
			} else {
				logger.info("Get scrollData with Jpql:page is not used, pageNo is "
						+ pageNo + " pageSize is " + pageSize);
			}
			resultData = query.getResultList();
		} catch (PersistenceException pe) {
			logger.error("Get scrollData with Jpql:get data failed,please see below information");
			throw new PersistenceException(pe.getMessage());
		}

		return resultData;
	}

	/**
	 * set the parameter to SQL dynamicly
	 * 
	 * @param query
	 * 
	 * @param queryParams
	 * 
	 */
	protected void setQueryParams(Query query, Object[] queryParams) {
		if (queryParams != Constant.OBJECT_NULL
				&& queryParams.length > Constant.ZERO) {
			for (int i = Constant.ZERO; i < queryParams.length; i++) {
				query.setParameter(i + Constant.ONE, queryParams[i]);
			}
		}
	}
//	public static IJpaDao getFromApplicationContext(ApplicationContext ctx) {
//		return (IJpaDao) ctx.getBean("jpaDao");
//	}
}
